Compiled components dropped caller attributes, and reactive children froze - #2
Merged
Merged
Conversation
added 3 commits
August 14, 2026 11:12
The root slot carries the plain-HTML props a caller passed, which is what makes `aria-label`, `title`, `data-testid` and `onClick` reach the DOM at all: a layout only ever sees `slot`, so there is no other route. That behaviour was gated on `!embedded`, and `embedded` is what the compiler sets on every component it produces, so a library built with this compiler dropped all of them from every component. Nothing reported it. The component rendered, looked right, and was missing whatever the caller passed. It surfaced as 72 failures out of 78 when one application moved onto a Layout-based library, and every one of them read as an application bug. The exemption looks deliberate: an embedded component does get every prop on `p`, so a layout could in principle place them itself. None does, and the compiler emits no such code, so the capability was theoretical and the loss was real. Recipe attributes still spread last, embedded or not, so a caller still cannot overwrite `class` or the `data-slot` that identifies the component. The test that asserted the old behaviour asserted the bug; it now asserts the fix.
The generated layout signature destructured its first parameter as
`({ slot, children }, p)`. The runtime exposes `children` as a getter over
Solid's resolved-children memo, so destructuring calls that getter once, at the
moment the layout runs, and freezes the result.
Solid's JSX compiler is what makes the difference: it wraps a member expression
in a getter it re-reads, and leaves a plain identifier alone. Emitting
`_stable.children` keeps the insert reactive.
What it cost: a `<For>` over a list that arrives asynchronously resolved to the
empty list it saw on the first pass and never ran again. In a browser built on
this, no `<web-view>` was ever created, so every site loaded correctly into a
document that was never attached and the window stayed blank. In an application,
a button's label stuck on its first value and two test crashes turned out to be
downstream of an element that never updated.
`slot` moves onto the parameter with it, so the signature has one shape, and
because a layout may use `children` without ever mentioning `slot` and that case
used to compile to an unbound reference.
Folded in from concurrent work in the shared checkout rather than written twice:
the diagnosis and the two tests are theirs.
Two silent defects, one in each half. Both are the kind that a consumer cannot diagnose from their own code, so this wants to reach the fleet before any more of it is ported.
added 2 commits
August 14, 2026 12:34
The application compiler resolves a consumer's declared runtime by reading `solid-layouts`'s built entry off disk, and nothing in this job built it, so the step failed on a missing `dist/index.js`. master has been red on this since `fc96dee`, which is long enough that the signal had stopped meaning anything. The failure reads as a broken resolver rather than an unbuilt dependency, which is presumably why it sat.
The compiler emits `_stable.children` and `_stable.slot` now, so the four generated components in the fixture change shape. The guard that compares them against a fresh run is what caught it, which is the guard doing its job.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects, one in each half of the project. Both are silent: no warning, no type error, no failed build. The component renders, looks right, and is wrong.
They were found by porting an application with 561 frontend tests onto a Layout-based library. It passes all 561 on the old library. On the new one it dropped to 483 passing, 78 failing, and not one of the failures was in the application or the library.
Compiled components dropped every plain HTML attribute
The root slot carries the props a caller passed that the recipe does not declare. That is what makes
aria-label,title,data-testidandonClickreach the DOM at all, because a layout only ever seesslotand has no other route. The behaviour was gated:embeddedis what the compiler sets on every component it produces, so the gate cancelled the behaviour for an entire real library. A stock button:Nothing the caller passed survived. 72 of the 78 failures were this one condition, and each one read as an application bug.
The exemption looks deliberate rather than accidental: an embedded component does receive every prop on
p, so a layout could in principle place them itself. Nothing does, and the compiler emits no such code, so the capability was theoretical and the loss was real. Recipe attributes still spread last, embedded or not, so a caller still cannot overwriteclassordata-slot.The existing test asserted the old behaviour, which means it asserted the bug. It now asserts the fix, and a second test covers the reported symptom directly.
childrenwas destructured, so reactive children frozeThe generated signature was
({ slot, children }, p). The runtime exposeschildrenas a getter over Solid's resolved-children memo, so destructuring calls it once, when the layout runs, and freezes whatever it returns.Solid's JSX compiler decides reactivity by the shape of the expression: a member expression becomes a getter it re-reads, a plain identifier is read once. Emitting
_stable.childrenis what keeps the insert live.Twelve lines reproduce it:
This is why a browser built on this rendered blank pages. A
<For>over a tab list that arrives asynchronously resolved to the empty list it saw on the first pass and never ran again, so no<web-view>element was ever created and every site loaded correctly into a document that was never attached. The workaround there was to keep a plain element between the Layout and theFor, described in comments as a rule of Layouts. It is not a rule, and those comments should come out.The diagnosis and the two compiler tests here are folded in from concurrent work in the shared checkout rather than written twice.
Verification
solid-layoutsruntimelayouts-transformcratelibrary.test.jsapplication.test.jsWhat these have in common
Neither is caught by the existing suites, because they exercise recipes and class resolution rather than a mounted component with a caller's attributes and a changing child. A single fixture that renders one compiled Layout with an
aria-labeland a signal child would have caught both, and is worth adding regardless of these fixes.Versions bumped for release:
solid-layouts0.1.3,solid-layouts-oxc0.1.7,rsbuild-plugin-solid-layouts0.1.4. Publishing is by tag through Trusted Publishing, so it happens from CI after this merges rather than from anyone's laptop, which is also the only way the native package gets a binary per platform.Also fixes CI itself.
masterhas been red sincefc96deebecause the application-host tests readsolid-layouts's built entry off disk and nothing in that job built it. The failure reads as a broken resolver rather than an unbuilt dependency, which is presumably why it sat.